// CoreCalc, a spreadsheet core implementation 

// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft and others

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using C5;

namespace CoreCalc.SheetDefinedFunctions
{
  // sestoft: This probably needs to be changed to a type hierarchy to 
  // accommodate strongly typed functions and matrices
  public enum Typ { Error, Number, Text, Array, Function, Value };

  public enum MarkLevel
  {
    MarkIII,
    MarkIV
  } ;

  /// <summary>
  /// All global methods and state for code generation
  /// </summary>
  public abstract class CodeGenerate
  {
    /// <summary>
    /// When true, the evaluation conditions take into account that AND
    /// and OR have short-circuit evaluation.  This gives more accurate
    /// but more complex evaluation conditions, which may be undesirable.
    /// </summary>
    public readonly bool SHORTCIRCUIT_EVALCONDS = false;

    /// <summary>
    /// The IL generator for a single function body, used by the
    /// CGExpr compile functions.  Set by the Initialize method, 
    /// which must be called before any compilation methods.
    /// </summary>
    public static ILGenerator ilg;

    /// <summary>
    /// These temporaries are used in code that tests whether a Value 
    /// is a NumberValue, whether a double is proper, and so on
    /// </summary>
    protected static LocalBuilder testValue;
    protected static LocalBuilder testDouble;
    protected static LocalBuilder tmpFunction;

    protected static readonly MethodInfo isInfinityMethod
      = typeof(double).GetMethod("IsInfinity", new Type[] { typeof(double) });
    protected static readonly MethodInfo isNaNMethod
      = typeof(double).GetMethod("IsNaN", new Type[] { typeof(double) });

    /// <summary>
    /// Maps cell address to a variable that holds the cell's value 
    /// as a double, if any such variable exists
    /// </summary>
    private static Dictionary<FullCellAddr, Variable> numberVariables;

    /// <summary>
    /// Maps cell address to a variable that holds the cell's value 
    /// as a Value object
    /// </summary>
    private static Dictionary<FullCellAddr, Variable> cellReferences;

    /// <summary>
    /// Initializes the CodeGenerator infrastructure, binds the ILGenerator, 
    /// creates temporaries; resets evaluation condition caches.
    /// Ready to compile a new SDF.
    /// </summary>
    /// <param name="ilg">The ILGenerator used by all compile methods</param>
    public static void Initialize(ILGenerator ilg)
    {
      CodeGenerate.ilg = ilg;
      numberVariables = new Dictionary<FullCellAddr, Variable>();
      testDouble = ilg.DeclareLocal(typeof(double));
      testValue = ilg.DeclareLocal(Value.type);
      tmpFunction = ilg.DeclareLocal(FunctionValue.type);
      CGCachedExpr.Reset();
    }

    // The errorIndex is an index into the ErrorValue.errorTable arraylist
    protected static void LoadError(int errorIndex)
    {
      ilg.Emit(OpCodes.Ldc_I4, errorIndex);
      ilg.Emit(OpCodes.Call, ErrorValue.fromIndexMethod);
    }

    protected static void LoadError(String message)
    {
      // Reuse, or create and reuse, an ErrorValue with the given message
      ilg.Emit(OpCodes.Ldc_I4, ErrorValue.GetIndex(message));
      ilg.Emit(OpCodes.Call, ErrorValue.fromIndexMethod);
    }

    protected static void LoadErrorNan(String message)
    {
      ilg.Emit(OpCodes.Ldc_R8, ErrorValue.MakeNan(ErrorValue.GetIndex(message)));
    }

    protected static Generate GenLoadError(int errorIndex)
    {
      return new Generate(delegate { LoadError(errorIndex); });
    }

    // The code generated by this method expects that the local var testDouble 
    // contains a NaN or Inf representing the error
    protected static Generate GenLoadError()
    {
      return new Generate(delegate
      {
        ilg.Emit(OpCodes.Ldloc, testDouble);
        WrapDoubleToNumberValue();
      });
    }

    protected static void SetArgCountErrorNan()
    {
      ilg.Emit(OpCodes.Ldc_R8, ErrorValue.argCountErrorValueNan);
      ilg.Emit(OpCodes.Stloc, testDouble);
    }

    protected static void LoadArgTypeErrorNan()
    {
      ilg.Emit(OpCodes.Ldc_R8, ErrorValue.argTypeErrorValueNan);
    }

    public static MarkLevel markLevel = MarkLevel.MarkIV;

    public static Dictionary<FullCellAddr, Variable> NumberVariables
    {
      get { return numberVariables; }
    }

    public static Dictionary<FullCellAddr, Variable> CellReferences
    {
      get { return cellReferences; }
    }

    /// <summary>
    /// Check the type and leave the value on top of the stack if isType()
    /// </summary>
    /// <param name="t"></param>
    /// <param name="ifType"></param>
    /// <param name="ifOther"></param>
    protected void CheckType(Type t, Generate ifType, Generate ifOther)
    {
      ilg.Emit(OpCodes.Stloc, testValue);
      ilg.Emit(OpCodes.Ldloc, testValue);
      ilg.Emit(OpCodes.Isinst, t);
      ilg.Emit(OpCodes.Brfalse, ifOther.GetLabel(ilg));
      ilg.Emit(OpCodes.Ldloc, testValue);
      ifType.Gen(ilg);
      if (!ifOther.Generated)
      {
        Label endLabel = ilg.DefineLabel();
        ilg.Emit(OpCodes.Br, endLabel);
        ifOther.Gen(ilg);
        ilg.MarkLabel(endLabel);
      }
    }

    protected void UnwrapToString(Generate ifString, Generate ifError)
    {
      CheckType(TextValue.type,
          new Generate(delegate
          {
            ilg.Emit(OpCodes.Ldfld, TextValue.valueField);
            ifString.Gen(ilg);
          }),
        ifError);
    }

    // Convert NumberValue to the enclosed float64, convert ErrorValue to its
    // NaN representation, and convert anything else to the NaN for ArgTypeError
    protected void UnwrapToDoubleOrNan()
    {
      ilg.Emit(OpCodes.Call, Value.toDoubleOrNanMethod);
    }

    public static void WrapDoubleToNumberValue()
    {
      ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
    }

    /// <summary>
    /// Compute least upper bound of two types in the type lattice 
    /// </summary>
    /// <param name="typ1"></param>
    /// <param name="typ2"></param>
    /// <returns></returns>
    protected Typ Lub(Typ typ1, Typ typ2)
    {
      if (typ1 == typ2) { return typ1; }
      else
      {
        switch (typ1)
        {
          case Typ.Error:
            return typ2;
          case Typ.Number:
          case Typ.Text:
          case Typ.Array:
          case Typ.Function:
            return typ2 == Typ.Error ? typ1 : Typ.Value;
          case Typ.Value:
            return Typ.Value;
          default:
            throw new ImpossibleException("Lub(Typ, Typ)");
        }
      }
    }

    internal static void SetVariables(Dictionary<FullCellAddr, Variable> addressToVariable)
    {
      cellReferences = addressToVariable;
    }

    /// <summary>
    /// Generates code to allocate an array vs of type Value[], of length arity,
    /// then evaluate each argument expression es[i] to a Value and storing it 
    /// in vs[i].  Leaves vs on the stack top.
    /// </summary>
    /// <param name="arity">The total number of arguments</param>
    /// <param name="offset">The number of given argument expressions to ignore</param>
    /// <param name="es">The given (partial) argument expressions</param>
    internal static void CompileToValueArray(int arity, int offset, CGExpr[] es)
    {
      CreateValueArray(arity);
      CompileExpressionsAndStore(offset, es);
    }

    /// <summary>
    /// Assumes an array vs of type Value[] is on the stack. 
    /// Generates code to evaluate each expression es[sourceOffset...] and  
    /// store it into array vs[targetOffset...] on the stack top.
    /// Leaves array vs on the stack top.
    /// </summary>
    /// <param name="offset">The number of given argument expressions to ignore</param>
    /// <param name="es">The given (partial) argument expressions</param>
    internal static void CompileExpressionsAndStore(int sourceOffset, CGExpr[] es)
    {
      for (int i = sourceOffset; i < es.Length; i++)
      {
        ilg.Emit(OpCodes.Dup);
        ilg.Emit(OpCodes.Ldc_I4, i - sourceOffset);
        es[i].Compile();
        ilg.Emit(OpCodes.Stelem_Ref);
      }
    }

    /// <summary>
    /// Generates code to allocate an array, as in new Value[arity]
    /// </summary>
    /// <param name="arity">The size of the new array</param>
    internal static void CreateValueArray(int arity)
    {
      ilg.Emit(OpCodes.Ldc_I4, arity);
      ilg.Emit(OpCodes.Newarr, Value.type);
    }
  }
}
